﻿using System.Collections;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;

namespace SDL
{
    using DockElementList = ObservableList<IDockElement>;
    using System;

    // An element which stacks any number of child elements horizontally 
    // or vertically.
    [ContentProperty("Elements")]
    public class DockGroup : FrameworkElement, IDockElement, IDockContainer
    {
        /////////////////////////////////////////////////////////////////////////////
        // Properties

        public DockElementList Elements { get; set; }

        /////////////////////////////////////////////////////////////////////////////
        // Default Constructor

        public DockGroup()
        {
            Loaded += new RoutedEventHandler(DockGroup_Loaded);

            Elements = new DockElementList();
            Elements.ListChanged += new ListChangedEventHandler(Elements_Changed);
        }

        /////////////////////////////////////////////////////////////////////////////
        // IElement

        public FrameworkElement AsFrameworkElement()
        {
            return this;
        }

        /////////////////////////////////////////////////////////////////////////////
        // IDockElement

        public Orientation Orientation { get; private set; }

        public void UpdateOrientation()
        {
            // find the desired orientation
            Orientation desiredOrientation;
            // TODO: maybe an IDockContainer.GetOrientation()
            IDockElement parent = Parent as IDockElement;
            if( parent != null )
            {
                // if parent is an element, the orientation is inverse
                switch( parent.Orientation )
                {
                case Orientation.Horizontal:
                    desiredOrientation = Orientation.Vertical;
                    break;
                case Orientation.Vertical:
                default:
                    desiredOrientation = Orientation.Horizontal;
                    break;
                }
            }
            else
            {
                // if parent is not an element, it is the frame
                switch( DockManager.GetEdge(this) )
                {
                case Edge.Left:
                case Edge.Right:
                    desiredOrientation = Orientation.Vertical;
                    break;
                case Edge.Top:
                case Edge.Bottom:
                default:
                    desiredOrientation = Orientation.Horizontal;
                    break;
                }
            }

            // apply the desired orientation
            if( desiredOrientation != Orientation )
            {
                Orientation = desiredOrientation;
                InvalidateMeasure();
            }
        }

        public bool CheckDrop(IDockElement dockElement, Point location)
        {
            // if the drag location is inside the dock group, check the children
            if( VisualTreeHelper.HitTest(this, PointFromScreen(location)) != null )
            {
                foreach( IDockElement element in Elements )
                {
                    if( element.CheckDrop(dockElement, location) ) return true;
                }
            }
            return false;
        }

        /////////////////////////////////////////////////////////////////////////////
        // IDockContainer

        public void Remove(IDockElement element, IDockElement replacement)
        {
            int index = Elements.IndexOf(element);
            Elements.RemoveAt(index);
            if( replacement != null )
            {
                Elements.Insert(index, replacement);
            }
            else
            {
                if( Elements.Count == 1 )
                {
                    // if the stack has only one child get rid of it
                    replacement = Elements[0];
                    Elements.RemoveAt(0);
                    _container.Remove(this, replacement);
                }
            }
        }

        public bool StartDrag(IDockElement element)
        {
            // pass it up the tree
            return _container.StartDrag(element);
        }

        public void AutoSize(Size size)
        {
        }

        public void TearOff(IDockElement element, Point offset, Size size)
        {
            // check if the element is an immediate child to this stack
            foreach( IDockElement dockElement in Elements )
            {
                if( dockElement == element )
                {
                    // remove the element from the children
                    Elements.Remove(dockElement);

                    // if the stack contains only one element, remove the stack
                    if( Elements.Count == 1 )
                    {
                        IDockElement replacement = Elements[0];
                        Elements.RemoveAt(0);
                        _container.Remove(this, replacement);
                    }

                    // no need to continue
                    break;
                }
            }

            // pass the tear off up the tree
            _container.TearOff(element, offset, size);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Event Handlers

        private void DockGroup_Loaded(object sender, RoutedEventArgs e)
        {
            _container = Parent as IDockContainer;
        }

        private void Elements_Changed(object sender, ListChangedEventArgs e)
        {
            if( e.NewItems != null )
            {
                foreach( IDockElement element in e.NewItems )
                {
                    // add the element to the logical and visual trees
                    AddLogicalChild(element);
                    AddVisualChild(element.AsFrameworkElement());
                }
            }
            if( e.OldItems != null )
            {
                foreach( IDockElement element in e.OldItems )
                {
                    // remove the element from the logical and visual trees
                    RemoveLogicalChild(element);
                    RemoveVisualChild(element.AsFrameworkElement());
                }
                InvalidateMeasure();
            }
        }

        /////////////////////////////////////////////////////////////////////////////
        // Private Data

        private IDockContainer _container;

        /////////////////////////////////////////////////////////////////////////////
        // FrameworkElement

        protected override int VisualChildrenCount
        {
            get
            {
                return Elements.Count;
            }
        }

        protected override IEnumerator LogicalChildren
        {
            get
            {
                foreach( IDockElement element in Elements )
                {
                    yield return element;
                }
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            return Elements[index].AsFrameworkElement();
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            // go through all the children, keep track of the total desired size
            Size totalDesired = new Size(0, 0);
            for( int i = 0; i < Elements.Count; ++i )
            {
                IDockElement element = Elements[i];

                // measure the element assuming it has infinite space
                element.Measure(availableSize);
                Size elementDesired = element.DesiredSize;

                // update the total desired size depending on the orientation
                if( Orientation == Orientation.Horizontal )
                {
                    totalDesired.Width += elementDesired.Width;
                    if( elementDesired.Height > totalDesired.Height )
                    {
                        totalDesired.Height = elementDesired.Height;
                    }
                }
                else
                {
                    totalDesired.Height += elementDesired.Height;
                    if( elementDesired.Width > totalDesired.Width )
                    {
                        totalDesired.Width = elementDesired.Width;
                    }
                }
            }

            // return the total desired size
            return totalDesired;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            // go through all the children, keeping track of available space
            Rect availableRect = new Rect(finalSize);
            for( int i = 0; i < Elements.Count; ++i )
            {
                // get the current element
                IDockElement element = Elements[i];

                // find the rectangle for the current element
                Rect elementRect = availableRect;
                if( i != Elements.Count - 1 )
                {
                    // get the desired size of the element
                    Size desiredSize = element.DesiredSize;

                    // adjust the element and available rectangles
                    if( Orientation == Orientation.Horizontal )
                    {
                        elementRect.Width = desiredSize.Width;
                        availableRect.X += desiredSize.Width;
                        availableRect.Width -= desiredSize.Width;
                    }
                    else
                    {
                        elementRect.Height = desiredSize.Height;
                        availableRect.Y += desiredSize.Height;
                        availableRect.Height -= desiredSize.Height;
                    }
                }

                // apply the rectangle to the element
                element.Arrange(elementRect);
            }

            // all available space is used
            return finalSize;
        }
    }
}
